/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package main.Weighting;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Pattern;
import main.KBProcess.Element;
import main.NER.Trie.NER_Candidate;
import main.NER.Branch;
import main.NER.Stem;
import main.NER.Trie.CandidateExtractor.BOOTSTRAP;
import main.NER.Trie.ContextTrie;
import main.NER.Trie.ContextTrie.ORIENTATION;
import main.NER.Trie.KBTrie;
import main.Ontology.ElementOntologySet;
import main.Ontology.EntityTypeOntology;

/**
 *
 * @author Isaac Osesina
 *
 * This class evaluates candidate scores
 * The evaluation is done based on the document class used to extract the candidate
 *
 */
public class WeightingCandidates implements Serializable{

    KBTrie kbTrie;
    Map<ElementOntologySet, BranchTypeDistribution> branchTypeMap;
    
    public WeightingCandidates(KBTrie kbTrie) {
        if (kbTrie == null) {
            return;
        }
        this.kbTrie = kbTrie;
        getStats();
    }

    private void getStats() {
        getContextScoreDist(ORIENTATION.LEFT, BOOTSTRAP.YES);
        getContextScoreDist(ORIENTATION.LEFT, BOOTSTRAP.NO);
        getContextScoreDist(ORIENTATION.RIGHT, BOOTSTRAP.YES);
        getContextScoreDist(ORIENTATION.RIGHT, BOOTSTRAP.NO);
        getIntrinsicDist();
    }

    private void getContextScoreDist(ORIENTATION orientation, BOOTSTRAP bootStrap) {
        ContextTrie trie = kbTrie.getCONTEXT_TRIE(orientation);
        Set<Entry<String, Stem>> entrySet_CONTEXT = trie.getEntrySet_CONTEXT(bootStrap);
        if(entrySet_CONTEXT != null) {
            Map<ElementOntologySet, List<Double>> scoresByBranchType = new HashMap<ElementOntologySet, List<Double>>();
            for(Entry<String, Stem> entry :entrySet_CONTEXT) {
                if(entry != null) {
                    List<Branch> youngestBranches = entry.getValue().youngestBranches();
                    if(youngestBranches != null) {
                        for(Branch b:youngestBranches) {
                            if(b != null) {
                                ElementOntologySet branchID = b.getFragmentSample().getOntologies();
                                List<Double> values = scoresByBranchType.get(branchID);
                                if(values==null) {
                                    values = new ArrayList<Double>();
                                    scoresByBranchType.put(branchID, values);
                                }
                                values.add(b.getMyTypeProportion());
                            }
                        }
                    }
                }
            }
            getContextScoreDistribution(orientation, bootStrap, scoresByBranchType);
        }
    }

    private void getContextScoreDistribution(ORIENTATION orientation, BOOTSTRAP bootStrap, Map<ElementOntologySet, List<Double>> scoresByBranchType) {
        if(scoresByBranchType != null) {
            branchTypeMap = branchTypeMap==null ? new HashMap<ElementOntologySet, BranchTypeDistribution>():branchTypeMap;
            for(ElementOntologySet branchID : scoresByBranchType.keySet()) {
                NormalDistribution distr = AbstractWeightingFunction.getNormalDistribution(scoresByBranchType.get(branchID));
                BranchTypeDistribution branchDistr = branchTypeMap.get(branchID)==null ? new BranchTypeDistribution():branchTypeMap.get(branchID);
                branchDistr.setContextDistribution(orientation, bootStrap, distr);
            }
        }
    }

    private void getIntrinsicDist() {
        Map<ElementOntologySet, List<Element>> entityList = kbTrie.getEntityList_Map();
        if(entityList != null) {
            Map<ElementOntologySet, List<Double>> scoresByBranchType = new HashMap<ElementOntologySet, List<Double>>();
            for(ElementOntologySet branchID:entityList.keySet()) {
                List<Element> eList = entityList.get(branchID);
                if(eList != null) {
                    for(Element e:eList) {
                        List<Element> tmp = new ArrayList<Element>(eList);
                        tmp.remove(e);
                        Map<ElementOntologySet, Pattern> entityPatterns = kbTrie.getEntityPatterns(e, tmp);
                        if(entityPatterns != null && entityPatterns.get(branchID) != null) {
                            List<Double> values = scoresByBranchType.get(branchID);
                            if(values==null) {
                                values = new ArrayList<Double>();
                                scoresByBranchType.put(branchID, values);
                            }
                            double intrScore = AbstractWeightingFunction.getIntrinsicProbability(e.getValue(), entityPatterns.get(branchID));
                            values.add(intrScore);
                        }
                    }
                }
            }
            getIntrinsicScoreDist(scoresByBranchType);
        }
    }

    private void getIntrinsicScoreDist(Map<ElementOntologySet, List<Double>> scoresByBranchType) {
        if(scoresByBranchType != null) {
            branchTypeMap = branchTypeMap==null ? new HashMap<ElementOntologySet, BranchTypeDistribution>():branchTypeMap;
            for(ElementOntologySet branchID : scoresByBranchType.keySet()) {
                NormalDistribution distr = AbstractWeightingFunction.getNormalDistribution(scoresByBranchType.get(branchID));
                BranchTypeDistribution branchDist = branchTypeMap.get(branchID)==null ? new BranchTypeDistribution():branchTypeMap.get(branchID);
                branchDist.setIntrinsicDist(distr);
            }
        }
    }


    private WeightingMethod selectWeightingClass(String weightingMethod) {

        if (weightingMethod.equalsIgnoreCase(WeightingMethod.BAYESIAN) && kbTrie.getEntityList()!=null) {
            return new BayesianWeightingFunction(kbTrie.getEntityList());
        } else if (weightingMethod.equalsIgnoreCase(WeightingMethod.DISPERSION)) {
            return new DispersionWeightingFunction();
        } else if (weightingMethod.equalsIgnoreCase(WeightingMethod.DEMPSTERSHAFER)) {
            return new DempsterShaferWeightingFunction();
        } else {
            return null;
        }
    }


    public NER_Candidate scoreCandidates(NER_Candidate candidate, BOOTSTRAP bootStrap,
            WeightingMethod weightingClass, double lowerBoundAsSDFactor_intrinsic,
            double lowerBoundAsSDFactor_LCxt, double lowerBoundAsSDFactor_RCxt) {

        if(candidate==null || weightingClass==null)
            return candidate;

//                if(cand.getEntityValue().equalsIgnoreCase("Levoyd J. Humphries") || cand.getEntityValue().equalsIgnoreCase("78"))
//                    System.out.println("cand"+cand);

            double intrinsicThreshold = lowerBoundAsSDFactor_intrinsic;
            double intrinsicMean = 0.0;
            double intrinsicSD = 0.0;
            double lCxtThreshold = lowerBoundAsSDFactor_LCxt;
            double rCxtThreshold = lowerBoundAsSDFactor_RCxt;
            ElementOntologySet branchID = candidate.getOntologies();
            if(branchTypeMap.get(branchID)==null)
                if (branchTypeMap.get(branchID).intrinsicDist == null ||
                    branchTypeMap.get(branchID).getContextDist(ORIENTATION.LEFT, bootStrap)== null ||
                    branchTypeMap.get(branchID).getContextDist(ORIENTATION.RIGHT, bootStrap) == null)
            {
                return candidate;
            }

                if (lowerBoundAsSDFactor_intrinsic != 0) {
                    NormalDistribution dist = branchTypeMap.get(branchID).intrinsicDist;
                    intrinsicSD = dist.getStandardDeviation();
                    intrinsicMean = dist.getMean();
                    intrinsicThreshold = intrinsicMean + (intrinsicSD * lowerBoundAsSDFactor_intrinsic);
                    double max = dist.getArray()[dist.getMaxIdx()];
                    intrinsicThreshold = intrinsicThreshold > max ? max : intrinsicThreshold;
                }

                if (lowerBoundAsSDFactor_LCxt != 0) {
                    NormalDistribution dist = branchTypeMap.get(branchID).getContextDist(ORIENTATION.LEFT, bootStrap);
                    double lCxtSD = dist.getStandardDeviation();
                    double lCxtMean = dist.getMean();
                    lCxtThreshold = lCxtMean + (lCxtSD * lowerBoundAsSDFactor_LCxt);
                    double max = dist.getArray()[dist.getMaxIdx()];
                    lCxtThreshold = lCxtThreshold > max ? max : lCxtThreshold;
                }

                if (lowerBoundAsSDFactor_RCxt != 0) {
                    NormalDistribution dist = branchTypeMap.get(branchID).getContextDist(ORIENTATION.RIGHT, bootStrap);
                    double rCxtSD = dist.getStandardDeviation();
                    double rCxtMean = dist.getMean();
                    rCxtThreshold = rCxtMean + (rCxtSD * lowerBoundAsSDFactor_RCxt);
                    double max = dist.getArray()[dist.getMaxIdx()];
                    rCxtThreshold = rCxtThreshold > max ? max : rCxtThreshold;
                }

            WeightingScore weightingScore = weightingClass.getEvaluateScore(candidate, intrinsicThreshold, lCxtThreshold, rCxtThreshold);
            double evaluateScore = weightingScore.getFinalscore();
            if (evaluateScore > 0) {
                //If mean = 0 and sd=0, then all known values have virtually nothing in common.
                //Hence, any candidate value must have an exact match.
                if (lowerBoundAsSDFactor_intrinsic != 0 && intrinsicMean == 0 && intrinsicSD == Double.MIN_VALUE && weightingScore.getIntrinsicScore() < 1.0) {
                    return candidate;
                }
                candidate.setScore(evaluateScore);
                candidate.setWeightingScore(weightingScore);
            }
        return candidate;
    }


    /**
     * This method calculates the candidate scores
     * @param bootStrap: Determines if candidates were extracted in bootstrap mode: If bootStrap is TRUE, then the
     * pruneableKeySet was used. If bootStrap is FALSE, then the terminal keySet was used.
     * @param candidates: ArrayList of candidates to be scored
     * @param weightingMethod: weighting method to be used e.g. Dispersion, Bayesian, DempsterSchafer etc.
     * @param lowerBoundAsSDFactor_intrinsic: Threshold of intrinsic score as a factor of SD
     * @param lowerBoundAsSDFactor_LCxt: Threshold of left context score as a factor of SD
     * @param lowerBoundAsSDFactor_RCxt: Threshold of right context score as a factor of SD
     * @return: ArrayList of candidates with score greater than zero if the weighting class can be found.
     */
    public List<NER_Candidate> scoreCandidates(List<NER_Candidate> candidates, BOOTSTRAP bootStrap, 
            String weightingMethod, double lowerBoundAsSDFactor_intrinsic,
            double lowerBoundAsSDFactor_LCxt, double lowerBoundAsSDFactor_RCxt) {
        List<NER_Candidate> nullScores = new ArrayList<NER_Candidate>();
        for(NER_Candidate c:candidates) {
            scoreCandidates(c, bootStrap, selectWeightingClass(weightingMethod), lowerBoundAsSDFactor_intrinsic, lowerBoundAsSDFactor_LCxt, lowerBoundAsSDFactor_RCxt);
            if(c.getWeightingScore() == null) {
                nullScores.add(c);
            }
        }
        candidates.removeAll(nullScores);
        return candidates;
    }



    /**
     * This method calculates the candidate scores by using either the mean or min values of the distribution
     * as thresholds for the intrinsic, left context and right context.
     * Otherwise it returns the input candidates list without modification.
     * @param bootStrap: Determines if candidates were extracted in bootstrap mode: If bootStrap is TRUE, then the
     * pruneableKeySet was used. If bootStrap is FALSE, then the terminal keySet was used.
     * @param candidates: ArrayList of candidates to be scored
     * @param weightingMethod: weighting method to be used e.g. Dispersion, Bayesian, DempsterSchafer etc.
     * @param mean: If mean==0 the mean value of the distribution us used as threshold, otherwise the min value is used
     * @return: ArrayList of candidates with score greater than zero if the weighting class can be found.
     */
    public List<NER_Candidate> scoreCandidates(BOOTSTRAP bootStrap, List<NER_Candidate> candidates,
            String weightingMethod, double lowerBoundAsSDFactor_Cxt, double lowerBoundAsSDFactor_intrinsic) {

        return scoreCandidates(candidates, bootStrap, weightingMethod, lowerBoundAsSDFactor_intrinsic, lowerBoundAsSDFactor_Cxt, lowerBoundAsSDFactor_Cxt);
    }

    /**
     * This method calculates the candidate scores by using 0 as thresholds for the intrinsic, left context
     * and right context.
     * @param bootStrap: Determines if candidates were extracted in bootstrap mode: If bootStrap is TRUE, then the
     * pruneableKeySet was used. If bootStrap is FALSE, then the terminal keySet was used.
     * @param candidates: ArrayList of candidates to be scored
     * @param weightingMethod: weighting method to be used e.g. Dispersion, Bayesian, DempsterSchafer etc.
     * @return: ArrayList of candidates with score greater than zero if the weighting class can be found.
     */
    public List<NER_Candidate> scoreCandidates(List<NER_Candidate> candidates, BOOTSTRAP bootStrap, String weightingMethod) {
        return scoreCandidates(candidates, bootStrap, weightingMethod, 0, 0, 0);
    }


    
    private class BranchTypeDistribution {
        //List<Double> intrinsicValues;
        NormalDistribution intrinsicDist;
        NormalDistribution leafEntryNormDist_LEFT;
        NormalDistribution nonAmbiguousNormDist_LEFT;
        NormalDistribution leafEntryNormDist_RIGHT;
        NormalDistribution nonAmbiguousNormDist_RIGHT;

        public BranchTypeDistribution() {
        }

        public void setIntrinsicDist(NormalDistribution intrinsicDist) {
            this.intrinsicDist = intrinsicDist;
        }

        public void setContextDistribution(ORIENTATION orientation, BOOTSTRAP bootStrap, NormalDistribution cxtDist) {
            if(orientation == ORIENTATION.LEFT) {
                if(bootStrap == BOOTSTRAP.YES)
                    leafEntryNormDist_LEFT = cxtDist;
                else
                   nonAmbiguousNormDist_LEFT = cxtDist;
            }
            else {
                if(bootStrap == BOOTSTRAP.YES)
                    leafEntryNormDist_RIGHT = cxtDist;
                else
                   nonAmbiguousNormDist_RIGHT = cxtDist;
            }
        }

        public NormalDistribution getContextDist(ORIENTATION orientation, BOOTSTRAP bootStrap) {
            if(orientation == ORIENTATION.LEFT) {
                if(bootStrap == BOOTSTRAP.YES)
                    return leafEntryNormDist_LEFT;
                else
                   return nonAmbiguousNormDist_LEFT;
            }
            else {
                if(bootStrap == BOOTSTRAP.YES)
                    return leafEntryNormDist_RIGHT;
                else
                   return nonAmbiguousNormDist_RIGHT;
            }
        }

    }



    


}
